package ch.ethz.fcl.metrobuzz.render.accessbility.view.local;

import java.awt.Color;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.vecmath.Vector2d;

import org.lwjgl.opengl.GL11;
import org.newdawn.slick.opengl.Texture;

import ch.ethz.fcl.metrobuzz.MBContainer;
import ch.ethz.fcl.metrobuzz.algorithm.network.shortest_path.public_transport.OneToAllDijkstraPublic;
import ch.ethz.fcl.metrobuzz.algorithm.network.shortest_path.public_transport.PathPublic;
import ch.ethz.fcl.metrobuzz.algorithm.network.shortest_path.public_transport.PathPublic.Route;
import ch.ethz.fcl.metrobuzz.applications.AccessibilityApp;
import ch.ethz.fcl.metrobuzz.data.MBData;
import ch.ethz.fcl.metrobuzz.data.scene.MBScene;
import ch.ethz.fcl.metrobuzz.data.scene.basemap.Edge;
import ch.ethz.fcl.metrobuzz.data.scene.basemap.Node;
import ch.ethz.fcl.metrobuzz.data.texture.MBTexture;
import ch.ethz.fcl.metrobuzz.data.transit.Stop;
import ch.ethz.fcl.metrobuzz.render.accessbility.AccessibilityRender;
import ch.ethz.fcl.metrobuzz.render.accessbility.interaction.AcceMouseAction;
import ch.ethz.fcl.metrobuzz.render.techniques.colors.MBColorMap;
import ch.ethz.fcl.metrobuzz.render.techniques.colors.MBColorTable;
import ch.ethz.fcl.metrobuzz.render.techniques.projection.Projection;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Point2D;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Rectangle;
import ch.ethz.fcl.metrobuzz.spatialindex.object3d.Point3D;
import ch.ethz.fcl.metrobuzz.tools.MBGL;
import ch.ethz.fcl.metrobuzz.tools.MBTools;

/**
 * Display all paths from selected stop to all other stops
 * 
 * @author Zeng Wei
 * @email zeng@arch.ethz.ch
 * 
 */
public class FlowMapView {
	private MBContainer container;
	private MBData data;
	private AccessibilityRender acceRender;
	private AcceMouseAction mouseAction;

	private OneToAllDijkstraPublic spPublic = null;
	private MBColorMap colorMap;

	private LinkedHashMap<Stop, PathPublic> publicPaths;
	Map<Edge, Integer> edgeWeightMap;

	private final String COLOR_MAP_LABEL = "Flow Volume";

	int maxWeight = -1;

	public FlowMapView(MBContainer container, AccessibilityRender acceRender) {
		this.container = container;
		this.data = container.getData();
		this.acceRender = acceRender;
		this.mouseAction = acceRender.getMouseAction();

		spPublic = new OneToAllDijkstraPublic(data);
	}

	public void update() {
		updatePaths();
	}

	public void render() {
		if (mouseAction.getOrigin() == null)
			return;

		renderWeightedPaths();
		renderColorMap();
	}

	@SuppressWarnings("unchecked")
	private void updatePaths() {
		if (mouseAction.getOrigin() == null)
			return;

		long time = System.currentTimeMillis();
		publicPaths = spPublic.getShortestPath(mouseAction.getOrigin(),
				3 * 60 * 60);

		System.out.println((System.currentTimeMillis() - time)
				+ " milli seconds used to compute shorted path size "
				+ publicPaths.size());
		time = System.currentTimeMillis();

		edgeWeightMap = new HashMap<Edge, Integer>();

		for (Stop s : publicPaths.keySet())
			publicPaths.get(s).init(data, edgeWeightMap);

		System.out.println((System.currentTimeMillis() - time)
				+ " milli seconds used to compute edge weight ");
		time = System.currentTimeMillis();

		edgeWeightMap = (Map<Edge, Integer>) MBTools.sortByComparator(
				edgeWeightMap, true);

		if (edgeWeightMap.size() == 0)
			return;

		List<Edge> edgeList = new ArrayList<Edge>(edgeWeightMap.keySet());
		maxWeight = edgeWeightMap.get(edgeList.get(edgeList.size() - 1));

		if (maxWeight != -1 && colorMap == null)
			colorMap = new MBColorMap(null, COLOR_MAP_LABEL, new String[] {
					"1", maxWeight + "" }, new float[] { 1, maxWeight });

		System.out.println((System.currentTimeMillis() - time)
				+ " milli seconds used to sort edge weight ");
	}

	private void renderWeightedPaths() {
		float cameraDist = container.getViewer().getCamera().getDistance();

		// set max distance as 15 pixels
		float maxWidth = container.getViewer().getProjection()
				.getWorldDistance(15);

		Map<Node, Boolean> halfHourNodeInMap = acceRender.getIsochroneView()
				.getHalfHourIsochrone().getNodeInMap();
		Map<Node, Boolean> oneHourNodeInMap = acceRender.getIsochroneView()
				.getOneHourIsochrone().getNodeInMap();

		for (Edge e : edgeWeightMap.keySet()) {
			int weight = edgeWeightMap.get(e);

			if (cameraDist >= 0.8f && weight < 2)
				continue;

			float ratio = (float) (Math.log10(weight) / Math.log10(maxWeight));

			Color color = colorMap.getColor(weight, 3, 90);
			int r = color.getRed(), g = color.getGreen(), b = color.getBlue();

			float alpha1 = 0.2f;
			if (halfHourNodeInMap.get(e.getStartNode()) != null)
				alpha1 = 1.0f;
			else if (oneHourNodeInMap.get(e.getStartNode()) != null)
				alpha1 = 0.7f;
			else
				alpha1 = 0.3f;

			float alpha2 = 0.2f;
			if (halfHourNodeInMap.get(e.getEndNode()) != null)
				alpha2 = 1.0f;
			else if (oneHourNodeInMap.get(e.getEndNode()) != null)
				alpha2 = 0.7f;
			else
				alpha2 = 0.3f;

			float width = maxWidth * ratio;

			// minimum width 1 pixel
			width = Math.max(container.getViewer().getProjection()
					.getWorldDistance(1), width);

			Vector2d dir, verDir;
			Point2D p1, p2, p3, p4;

			dir = e.getDir();
			verDir = MBTools.rotate(dir, Math.PI / 2);

			p1 = e.getStartNode().toPoint().getPointAt(verDir, width / 2);
			p2 = e.getEndNode().toPoint().getPointAt(verDir, width / 2);
			p3 = e.getEndNode().toPoint().getPointAt(verDir, -width / 2);
			p4 = e.getStartNode().toPoint().getPointAt(verDir, -width / 2);

			GL11.glBegin(GL11.GL_POLYGON);

			GL11.glColor4f(r / 255.0f, g / 255.0f, b / 255.0f, alpha1);
			p1.render();
			p4.render();

			GL11.glColor4f(r / 255.0f, g / 255.0f, b / 255.0f, alpha2);
			p3.render();
			p2.render();
			GL11.glEnd();

			Node end = e.getEndNode();
			for (Edge nextEdge : end.getOutEdges())
				fillGap(maxWidth, e, nextEdge);
		}
	}

	// fill gaps between consecutive neighboring edges, that is, to draw a
	// triangle between wide enough two edges
	private void fillGap(float maxWidth, Edge e1, Edge e2) {
		Integer volume1 = edgeWeightMap.get(e1);
		Integer volume2 = edgeWeightMap.get(e2);

		if (volume1 == null || volume2 == null)
			return;

		Map<Node, Boolean> halfHourNodeInMap = acceRender.getIsochroneView()
				.getHalfHourIsochrone().getNodeInMap();
		Map<Node, Boolean> oneHourNodeInMap = acceRender.getIsochroneView()
				.getOneHourIsochrone().getNodeInMap();

		float ratio1 = (float) (Math.log10(volume1) / Math.log10(maxWeight));
		float ratio2 = (float) (Math.log10(volume2) / Math.log10(maxWeight));

		// max width is 15 pixels, so 0.2f ratio is about 3 pixels
		// if (ratio1 < 0.2f || ratio2 < 0.2f)
		// return;

		Point2D intersect = e1.getEndNode().toPoint();

		Vector2d dir1 = e1.getDir(), dir2 = e2.getDir();

		double angle = MBTools.angle(dir1, dir2);

		if (e1.isParallel(e2))
			return;
		else {
			Color color = colorMap.getColor(volume1 / 2 + volume2 / 2, 3, 90);
			int r = color.getRed(), g = color.getGreen(), b = color.getBlue();

			float alpha = 0.2f;
			if (halfHourNodeInMap.get(e1.getStartNode()) != null
					&& halfHourNodeInMap.get(e1.getEndNode()) != null)
				alpha = 1.0f;
			else if (halfHourNodeInMap.get(e1.getStartNode()) != null
					&& halfHourNodeInMap.get(e1.getEndNode()) == null)
				alpha = 0.8f;
			else if (oneHourNodeInMap.get(e1.getStartNode()) != null
					&& oneHourNodeInMap.get(e1.getEndNode()) != null)
				alpha = 0.8f;

			GL11.glColor4f(r / 255.0f, g / 255.0f, b / 255.0f, alpha);
			Point2D p1, p2;
			if (angle > 0) {
				p1 = intersect.getPointAt(MBTools.rotate(dir1, Math.PI / 2),
						-maxWidth * ratio1 / 2);
				p2 = intersect.getPointAt(MBTools.rotate(dir2, Math.PI / 2),
						-maxWidth * ratio2 / 2);
			} else {
				p1 = intersect.getPointAt(MBTools.rotate(dir1, Math.PI / 2),
						maxWidth * ratio1 / 2);
				p2 = intersect.getPointAt(MBTools.rotate(dir2, Math.PI / 2),
						maxWidth * ratio2 / 2);
			}

			GL11.glBegin(GL11.GL_TRIANGLES);
			intersect.render();
			p1.render();
			p2.render();
			GL11.glEnd();

			// GL11.glColor4f(0, 0, 0, 0.3f);
			// GL11.glLineWidth(1.0f);
			// GL11.glBegin(GL11.GL_LINES);
			// p1.render();
			// p2.render();
			// GL11.glEnd();
		}
	}

	public void debugODPath() {
		PathPublic p = publicPaths.get(mouseAction.getDestination());
		List<Route> routes = p.getPath();
		List<List<Node>> exactPath = p.getExactPath();

		for (int i = 0; i < routes.size(); i++) {
			List<Node> onePath = exactPath.get(i);

			for (int j = 0; j < onePath.size() - 1; j++) {
				Node n1 = onePath.get(j);
				Node n2 = onePath.get(j + 1);

				Edge e = data.getScene().getEdge(n1, n2);

				if (e == null) {
					MBScene scene = data.getScene();

					e = new Edge(scene, n1.getId() + "" + n2.getId(),
							scene.getNodeIndex(n1.getId()),
							scene.getNodeIndex(n2.getId()));
				}

				System.out
						.println("Edge "
								+ e.getId()
								+ " flow volume "
								+ edgeWeightMap.get(e)
								+ " angle radian "
								+ Math.tanh(e.getDir().y / e.getDir().x)
								+ " degree "
								+ Math.toDegrees(Math.tanh(e.getDir().y
										/ e.getDir().x)));

				if (e.getId().equals("60510")) {

					Edge e2 = data.getScene().getEdge("60511");
					System.out.println("Angle diff "
							+ MBTools.angle(e.getDir(), e2.getDir()));
				}
			}
		}

	}

	// render the origin and destination stop
	public void renderODStop(Stop stop) {
		Point2D loc = stop.toPoint();

		if (loc == null)
			return;

		int screenSize = 12;

		int color = MBColorTable.BLACK;
		if (stop.equals(mouseAction.getOrigin()))
			color = MBColorTable.BLACK;
		else if (stop.equals(mouseAction.getDestination()))
			color = MBColorTable.BLACK;
		else {
			color = MBColorTable.GREY;
			screenSize = 2;
		}

		float size = container.getViewer().getProjection()
				.getWorldDistance(screenSize);

		MBColorTable.assignColor(color, 1.0f);
		MBGL.renderEclipse(loc.getX(), loc.getY(), 0, size, size, true);

		if (stop.equals(mouseAction.getOrigin())
				|| stop.equals(mouseAction.getDestination())) {
			GL11.glColor4f(1, 1, 1, 1);
			MBGL.renderEclipse(loc.getX(), loc.getY(), 0, size * 0.7f,
					size * 0.7f, true);

			MBColorTable.assignColor(color, 1.0f);
			MBGL.renderEclipse(loc.getX(), loc.getY(), 0, size * 0.3f,
					size * 0.3f, true);

			renderODSymbol(stop, 30);
		}
	}

	private void renderODSymbol(Stop stop, int screenSize) {
		float size = container.getViewer().getProjection()
				.getWorldDistance(screenSize);

		// od_symbol.png width and height
		// int width = 256;
		int height = 512;
		// coordinates of the symbol in the image
		// int minX = 45, minY = 120, maxX = 210;
		int maxY = 400;

		Point2D loc = stop.toPoint();

		float imgMinX = loc.getX() - size / 2;
		float imgMaxX = loc.getX() + size / 2;
		float imgMinY = loc.getY() - size * 2 * (height - maxY) / height;
		float imgMaxY = loc.getY() + size * 2 * (maxY) / height;

		Rectangle rec = new Rectangle(imgMinX, imgMinY, imgMaxX, imgMaxY);

		Texture symbol = null;
		if (stop.equals(mouseAction.getOrigin()))
			symbol = data.getTexture().getOriginSymbol();
		else if (stop.equals(mouseAction.getDestination()))
			symbol = data.getTexture().getDestinSymbol();

		MBTexture.renderTexture(symbol, rec);
	}

	private void renderColorMap() {
		Projection proj = container.getViewer().getProjection();

		int width = 250, height = 20;
		int screenWidth = AccessibilityApp.CANVAS_WIDTH;

		int rightAlignment = 60;
		Point3D min = proj.getWorldCoord(new Point2D(screenWidth - width
				- rightAlignment, 20), 0);

		Point3D max = proj.getWorldCoord(new Point2D(screenWidth
				- rightAlignment, 20 + height), 0);

		colorMap.render(container, min.getX(), min.getY(), max.getX(),
				max.getY(), 90);
	}

	public AccessibilityRender getAcceRender() {
		return acceRender;
	}

	public Map<Stop, PathPublic> getPathMap() {
		return publicPaths;
	}
}
